package com.xmjs.baselibrary.network.retrofit;

import com.trello.rxlifecycle3.LifecycleTransformer;
import com.trello.rxlifecycle3.components.support.RxAppCompatActivity;
import com.trello.rxlifecycle3.components.support.RxFragment;
import com.xmjs.baselibrary.base.IBaseView;
import com.xmjs.baselibrary.base.dialog.RxDialog;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import io.reactivex.Observable;
import io.reactivex.ObservableEmitter;
import io.reactivex.ObservableOnSubscribe;
import io.reactivex.ObservableTransformer;
import io.reactivex.Scheduler;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.functions.Function;
import io.reactivex.schedulers.Schedulers;
import okhttp3.ResponseBody;

/**
 * RxJava 工具类
 */
public final class RxUtil {

    /**
     * 任务调度器：事件订阅在io线程，事件观察在主线程
     *
     * @param <T>
     * @return
     */
    public static <T> ObservableTransformer<T, T> applySchedulers() {
        return upstream -> upstream.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
    }

    /**
     * 绑定生命周期，防止内存泄漏
     *
     * @param view
     * @param <T>
     * @return
     */
    public static <T> LifecycleTransformer<T> bindToLifecycle(IBaseView view) {
        if (view instanceof RxAppCompatActivity) {
            return ((RxAppCompatActivity) view).bindToLifecycle();
        } else if (view instanceof RxFragment) {
            return ((RxFragment) view).bindToLifecycle();
        } else if (view instanceof RxDialog) {
            return ((RxDialog) view).bindToLifecycle();
        } else {
            throw new IllegalArgumentException("View isn't RxAppCompatActivity、RxFragment or RxDialog");
        }
    }

    /**
     * IO线程
     *
     * @return
     */
    public static Scheduler ioThread() {
        return Schedulers.io();
    }

    /**
     * UI主线程
     *
     * @return
     */
    public static Scheduler mainThread() {
        return AndroidSchedulers.mainThread();
    }

    /**
     * 文件下载转换工具，如果文件不存在，会创建文件；已存在，则被替换
     * <p>
     * 定义 retrofit 接口<br>
     * GET("https://github.com/binwin20/my.apk")<br>
     * Observable<ResponseBody> downloadFile();
     * <p>
     * 使用方式<br>
     * Api.downloadFile()<br>
     * .flatMap(RxUtils.downloadFlapMap(file))<br>
     * .compose(RxUtils.applySchedulers())<br>
     * .subscribe(progress -> showProgress(progress));
     *
     * @param saveFile 保存文件的地址
     * @return
     */
    public static Function<? super ResponseBody, ? extends Observable<Integer>> downloadFlapMap(File saveFile) {
        return responseBody -> Observable.create((ObservableOnSubscribe<Integer>) subscriber ->
                writeResponseBodyToDisk(responseBody, saveFile, subscriber))
                .compose(RxUtil.applySchedulers());
    }

    private static void writeResponseBodyToDisk(ResponseBody body, File file, ObservableEmitter<? super Integer> subscriber) {
        Throwable throwable = null;
        try {

            InputStream inputStream = null;
            OutputStream outputStream = null;
            long lastTime = System.currentTimeMillis();

            try {
                byte[] fileReader = new byte[4096];
                long fileSize = body.contentLength();
                long fileSizeDownloaded = 0;

                inputStream = body.byteStream();
                outputStream = new FileOutputStream(file);
                while (true) {
                    int read = inputStream.read(fileReader);
                    if (read == -1) {
                        break;
                    }

                    outputStream.write(fileReader, 0, read);
                    fileSizeDownloaded += read;
                    if (System.currentTimeMillis() - lastTime > 200) {
                        lastTime = System.currentTimeMillis();
                        subscriber.onNext((int) (fileSizeDownloaded * 100 / fileSize));
                    }
                }
                outputStream.flush();
            } catch (IOException e) {
                throwable = e;
            } finally {
                if (inputStream != null) {
                    inputStream.close();
                }
                if (outputStream != null) {
                    outputStream.close();
                }
            }
        } catch (IOException e) {
            throwable = e;
        } finally {
            if (throwable == null) {
                subscriber.onComplete();
            } else {
                subscriber.onError(throwable);
            }
        }
    }
}
